package com.hqd.ch03.v50.boot.context.properties;

import com.hqd.ch03.v50.beans.factory.config.BeanDefinition;
import com.hqd.ch03.v50.core.annotation.MergedAnnotation;
import com.hqd.ch03.v50.core.annotation.MergedAnnotations;
import com.hqd.ch03.v50.factory.BeanFactory;
import com.hqd.ch03.v50.factory.HierarchicalBeanFactory;
import com.hqd.ch03.v50.factory.ListableBeanFactory;
import com.hqd.ch03.v50.factory.support.RootBeanDefinition;
import com.hqd.ch03.v50.registry.BeanDefinitionRegistry;
import com.hqd.ch03.v50.utils.StringUtils;

final class ConfigurationPropertiesBeanRegistrar {

    private final BeanDefinitionRegistry registry;

    private final BeanFactory beanFactory;

    ConfigurationPropertiesBeanRegistrar(BeanDefinitionRegistry registry) {
        this.registry = registry;
        this.beanFactory = (BeanFactory) this.registry;
    }

    void register(Class<?> type) {
        MergedAnnotation<ConfigurationProperties> annotation = MergedAnnotations
                .from(type, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY).get(ConfigurationProperties.class);
        register(type, annotation);
    }

    void register(Class<?> type, MergedAnnotation<ConfigurationProperties> annotation) {
        String name = getName(type, annotation);
        if (!containsBeanDefinition(name)) {
            registerBeanDefinition(name, type, annotation);
        }
    }

    private String getName(Class<?> type, MergedAnnotation<ConfigurationProperties> annotation) {
        String prefix = annotation.isPresent() ? annotation.getString("prefix") : "";
        return (StringUtils.hasText(prefix) ? prefix + "-" + type.getName() : type.getName());
    }

    private boolean containsBeanDefinition(String name) {
        return containsBeanDefinition(this.beanFactory, name);
    }

    private boolean containsBeanDefinition(BeanFactory beanFactory, String name) {
        if (beanFactory instanceof ListableBeanFactory
                && ((ListableBeanFactory) beanFactory).containsBeanDefinition(name)) {
            return true;
        }
        if (beanFactory instanceof HierarchicalBeanFactory) {
            return containsBeanDefinition(((HierarchicalBeanFactory) beanFactory).getParentBeanFactory(), name);
        }
        return false;
    }

    private void registerBeanDefinition(String beanName, Class<?> type,
                                        MergedAnnotation<ConfigurationProperties> annotation) {
        this.registry.registerBeanDefinition(beanName, createBeanDefinition(beanName, type));
    }

    private BeanDefinition createBeanDefinition(String beanName, Class<?> type) {
        ConfigurationPropertiesBean.BindMethod bindMethod = ConfigurationPropertiesBean.BindMethod.forType(type);
        RootBeanDefinition definition = new RootBeanDefinition(type);
        definition.setAttribute(ConfigurationPropertiesBean.BindMethod.class.getName(), bindMethod);
        if (bindMethod == ConfigurationPropertiesBean.BindMethod.VALUE_OBJECT) {
            definition.setInstanceSupplier(() -> createValueObject(beanName, type));
        }
        return definition;
    }

    private Object createValueObject(String beanName, Class<?> beanType) {
        ConfigurationPropertiesBean bean = ConfigurationPropertiesBean.forValueObject(beanType, beanName);
        ConfigurationPropertiesBinder binder = ConfigurationPropertiesBinder.get(this.beanFactory);
        try {
            return binder.bindOrCreate(bean);
        } catch (Exception ex) {
            throw new ConfigurationPropertiesBindException(bean, ex);
        }
    }

}
